/*
 * Copyright (c) 2009 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.in;

import wtkx.Component;
import wtkx.Display;
import wtkx.wtk.ApplicationContext;
import wtkx.wtk.DragSource;
import wtkx.wtk.DropAction;
import wtkx.wtk.DropTarget;
import wtkx.wtk.Keyboard;
import wtkx.wtk.Manifest;
import wtkx.wtk.Mouse;
import wtkx.wtk.Point;
import wtkx.wtk.Platform;
import wtkx.wtk.Visual;
import wtkx.wtk.effects.Effect;

import java.awt.AWTEvent;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Transparency;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;

/**
 * Native AWT display host.
 *
 * @author gbrown
 */
public final class DisplayHost 
    extends java.awt.Container 
{

    public static DropAction getUserDropAction(InputEvent event) {
        DropAction userDropAction;

        if ((event.isControlDown() && event.isShiftDown())
            || (event.isAltDown() && event.isMetaDown())) {
            userDropAction = DropAction.LINK;
        } else if (event.isControlDown()
            || (event.isAltDown())) {
            userDropAction = DropAction.COPY;
        } else if (event.isShiftDown()){
            userDropAction = DropAction.MOVE;
        } else {
            userDropAction = null;
        }

        return userDropAction;
    }

    public static DropAction getDropAction(int nativeDropAction) {
        DropAction dropAction = null;

        switch (nativeDropAction) {
            case DnDConstants.ACTION_COPY: {
                dropAction = DropAction.COPY;
                break;
            }

            case DnDConstants.ACTION_MOVE: {
                dropAction = DropAction.MOVE;
                break;
            }

            case DnDConstants.ACTION_LINK: {
                dropAction = DropAction.LINK;
                break;
            }
        }

        return dropAction;
    }

    public static int getSupportedDropActions(int sourceActions) {
        int dropActions = 0;

        if ((sourceActions & DnDConstants.ACTION_COPY) > 0) {
            dropActions |= DropAction.COPY.getMask();
        }

        if ((sourceActions & DnDConstants.ACTION_MOVE) > 0) {
            dropActions |= DropAction.MOVE.getMask();
        }

        if ((sourceActions & DnDConstants.ACTION_LINK) > 0) {
            dropActions |= DropAction.LINK.getMask();
        }

        return dropActions;
    }

    public static int getNativeDropAction(DropAction dropAction) {
        int nativeDropAction = 0;

        if (dropAction != null) {
            switch(dropAction) {
                case COPY: {
                    nativeDropAction = DnDConstants.ACTION_COPY;
                    break;
                }

                case MOVE: {
                    nativeDropAction = DnDConstants.ACTION_MOVE;
                    break;
                }

                case LINK: {
                    nativeDropAction = DnDConstants.ACTION_LINK;
                    break;
                }
            }
        }

        return nativeDropAction;
    }

    public static java.awt.Cursor getDropCursor(DropAction dropAction) {
        // Update the drop cursor
        java.awt.Cursor cursor = java.awt.Cursor.getDefaultCursor();

        if (dropAction != null) {
            // Show the cursor for the drop action returned by the
            // drop target
            switch (dropAction) {
                case COPY: {
                    cursor = java.awt.dnd.DragSource.DefaultCopyDrop;
                    break;
                }

                case MOVE: {
                    cursor = java.awt.dnd.DragSource.DefaultMoveDrop;
                    break;
                }

                case LINK: {
                    cursor = java.awt.dnd.DragSource.DefaultLinkDrop;
                    break;
                }
            }
        }

        return cursor;
    }

    public class DropListener 
        implements DropTargetListener
    {

        DropListener(){
            super();
        }


        public void dragEnter(DropTargetDragEvent event) {
            if (dragDescendant != null) {
                throw new IllegalStateException("Local drag already in progress.");
            }

            java.awt.Point location = event.getLocation();
            mouseLocation = new Point(location.x, location.y);

            // Initialize drag state
            dragManifest = new RemoteManifest(event.getTransferable());

            // Initialize drop state
            userDropAction = getDropAction(event.getDropAction());

            // Notify drop target
            dropDescendant = getDropDescendant(location.x, location.y);

            DropAction dropAction = null;

            if (dropDescendant != null) {
                DropTarget dropTarget = dropDescendant.getDropTarget();
                dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                                  getSupportedDropActions(event.getSourceActions()), userDropAction);
            }

            if (dropAction == null) {
                event.rejectDrag();
            } else {
                event.acceptDrag(getNativeDropAction(dropAction));
            }
        }

        public void dragExit(DropTargetEvent event) {
            // Clear mouse location
            mouseLocation = null;

            // Clear drag state
            dragManifest = null;

            // Clear drop state
            userDropAction = null;

            if (dropDescendant != null) {
                DropTarget dropTarget = dropDescendant.getDropTarget();
                dropTarget.dragExit(dropDescendant);
            }

            dropDescendant = null;
        }

        public void dragOver(DropTargetDragEvent event) {
            java.awt.Point location = event.getLocation();

            // Get the previous and current drop descendant and call
            // move or exit/enter as appropriate
            Component previousDropDescendant = dropDescendant;
            dropDescendant = getDropDescendant(location.x, location.y);

            DropAction dropAction = null;

            if (previousDropDescendant == dropDescendant) {
                if (dropDescendant != null) {
                    DropTarget dropTarget = dropDescendant.getDropTarget();

                    Display display = DisplayHost.this.apcx.getDisplay();

                    Point dropLocation = dropDescendant.mapPointFromAncestor(display,
                                                                             location.x, location.y);
                    dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
                                                     getSupportedDropActions(event.getSourceActions()),
                                                     dropLocation.x, dropLocation.y, userDropAction);
                }
            } else {
                if (previousDropDescendant != null) {
                    DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
                    previousDropTarget.dragExit(previousDropDescendant);
                }

                if (dropDescendant != null) {
                    DropTarget dropTarget = dropDescendant.getDropTarget();
                    dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                                      getSupportedDropActions(event.getSourceActions()),
                                                      userDropAction);
                }
            }

            // Update cursor
            setCursor(getDropCursor(dropAction));

            if (dropAction == null) {
                event.rejectDrag();
            } else {
                event.acceptDrag(getNativeDropAction(dropAction));
            }
        }

        public void dropActionChanged(DropTargetDragEvent event) {
            userDropAction = getDropAction(event.getDropAction());

            DropAction dropAction = null;

            if (dropDescendant != null) {
                java.awt.Point location = event.getLocation();

                Display display = DisplayHost.this.apcx.getDisplay();

                Point dropLocation = dropDescendant.mapPointFromAncestor(display,
                                                                         location.x, location.y);

                DropTarget dropTarget = dropDescendant.getDropTarget();
                dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                             getSupportedDropActions(event.getSourceActions()), dropLocation.x, dropLocation.y,
                                                             userDropAction);
            }

            if (dropAction == null) {
                event.rejectDrag();
            } else {
                event.acceptDrag(getNativeDropAction(dropAction));
            }
        }

        public void drop(DropTargetDropEvent event) {
            java.awt.Point location = event.getLocation();
            dropDescendant = getDropDescendant(location.x, location.y);

            DropAction dropAction = null;

            if (dropDescendant != null) {

                Display display = DisplayHost.this.apcx.getDisplay();

                Point dropLocation = dropDescendant.mapPointFromAncestor(display,
                                                                         location.x, location.y);
                DropTarget dropTarget = dropDescendant.getDropTarget();

                // Simulate a user drop action change to get the current drop action
                int supportedDropActions = getSupportedDropActions(event.getSourceActions());

                dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                             supportedDropActions, dropLocation.x, dropLocation.y, userDropAction);

                if (dropAction != null) {
                    // Perform the drop
                    event.acceptDrop(getNativeDropAction(dropAction));
                    dropTarget.drop(dropDescendant, dragManifest,
                                    supportedDropActions, dropLocation.x, dropLocation.y, userDropAction);
                }
            }

            if (dropAction == null) {
                event.rejectDrop();
            }

            event.dropComplete(true);

            // Restore the cursor to the default
            setCursor(java.awt.Cursor.getDefaultCursor());

            // Clear drag state
            dragManifest = null;

            // Clear drop state
            dropDescendant = null;
        }
    }

    private ApplicationContext apcx;

    private Point mouseLocation;

    private Component focusedComponent;

    private Point dragLocation;
    private Component dragDescendant;

    private Manifest dragManifest;

    private DropAction userDropAction;
    private Component dropDescendant;

    private DropTargetListener dropTargetListener;

    public DisplayHost(ApplicationContext apcx) {
        super();
        if (null != apcx){
            this.apcx = apcx;
            this.enableEvents(AWTEvent.COMPONENT_EVENT_MASK
                              | AWTEvent.FOCUS_EVENT_MASK
                              | AWTEvent.MOUSE_EVENT_MASK
                              | AWTEvent.MOUSE_MOTION_EVENT_MASK
                              | AWTEvent.MOUSE_WHEEL_EVENT_MASK
                              | AWTEvent.KEY_EVENT_MASK);
            try {
                System.setProperty("sun.awt.noerasebackground", "true");
                System.setProperty("sun.awt.erasebackgroundonresize", "true");
            } catch (SecurityException exception) {
            }
            this.dropTargetListener = new DropListener();

            new java.awt.dnd.DropTarget(this, dropTargetListener);

            this.setFocusTraversalKeysEnabled(false);
        }
        else
            throw new IllegalArgumentException();
    }


    public Point getMouseLocation() {
        return mouseLocation;
    }

    @Override
    public void repaint(int x, int y, int width, int height) {
        // Ensure that the repaint call is properly bounded (some
        // implementations of AWT do not properly clip the repaint call
        // when x or y is negative: the negative value is converted to 0,
        // but the width/height is not adjusted)
        if (x < 0) {
            width = Math.max(width + x, 0);
            x = 0;
        }

        if (y < 0) {
            height = Math.max(height + y, 0);
            y = 0;
        }

        if (width > 0
            && height > 0) {
            super.repaint(x, y, width, height);
        }
    }

    @Override
    public void paint(Graphics graphics) {
        // Intersect the clip region with the bounds of this component
        // (for some reason, AWT does not do this automatically)
        ((Graphics2D)graphics).clip(new java.awt.Rectangle(0, 0, getWidth(), getHeight()));

        java.awt.Rectangle clipBounds = graphics.getClipBounds();
        if (clipBounds != null
            && !clipBounds.isEmpty()) {
            try {
                if (!paintVolatileBuffered((Graphics2D)graphics)) {
                    if (!paintBuffered((Graphics2D)graphics)) {
                        paintDisplay((Graphics2D)graphics);
                    }
                }
            } catch (RuntimeException exception) {
                System.out.println("Exception thrown during paint(): " + exception);
                throw exception;
            }
        }
    }

    /**
     * Attempts to paint the display using an offscreen buffer.
     *
     * @param graphics
     * The source graphics context.
     *
     * @return
     * <tt>true</tt> if the display was painted using the offscreen
     * buffer; <tt>false</tt>, otherwise.
     */
    private boolean paintBuffered(Graphics2D graphics) {
        boolean painted = false;

        // Paint the display into an offscreen buffer
        GraphicsConfiguration gc = graphics.getDeviceConfiguration();
        java.awt.Rectangle clipBounds = graphics.getClipBounds();
        java.awt.image.BufferedImage bufferedImage =
            gc.createCompatibleImage(clipBounds.width, clipBounds.height,
                                     Transparency.OPAQUE);

        if (bufferedImage != null) {
            Graphics2D bufferedImageGraphics = (Graphics2D)bufferedImage.getGraphics();
            bufferedImageGraphics.setClip(0, 0, clipBounds.width, clipBounds.height);
            bufferedImageGraphics.translate(-clipBounds.x, -clipBounds.y);

            try {
                paintDisplay(bufferedImageGraphics);
                graphics.drawImage(bufferedImage, clipBounds.x, clipBounds.y, this);
            } finally {
                bufferedImageGraphics.dispose();
            }

            painted = true;
        }

        return painted;
    }

    /**
     * Attempts to paint the display using a volatile offscreen buffer.
     *
     * @param graphics
     * The source graphics context.
     *
     * @return
     * <tt>true</tt> if the display was painted using the offscreen
     * buffer; <tt>false</tt>, otherwise.
     */
    private boolean paintVolatileBuffered(Graphics2D graphics) {
        boolean painted = false;

        // Paint the display into a volatile offscreen buffer
        GraphicsConfiguration gc = graphics.getDeviceConfiguration();
        java.awt.Rectangle clipBounds = graphics.getClipBounds();
        java.awt.image.VolatileImage volatileImage =
            gc.createCompatibleVolatileImage(clipBounds.width, clipBounds.height,
                                             Transparency.OPAQUE);

        // If we have a valid volatile image, attempt to paint the
        // display to it
        if (volatileImage != null) {
            int valid = volatileImage.validate(getGraphicsConfiguration());

            if (valid == java.awt.image.VolatileImage.IMAGE_OK
                || valid == java.awt.image.VolatileImage.IMAGE_RESTORED) {
                Graphics2D volatileImageGraphics = (Graphics2D)volatileImage.getGraphics();
                volatileImageGraphics.setClip(0, 0, clipBounds.width, clipBounds.height);
                volatileImageGraphics.translate(-clipBounds.x, -clipBounds.y);

                try {
                    paintDisplay(volatileImageGraphics);
                    graphics.drawImage(volatileImage, clipBounds.x, clipBounds.y, this);
                } finally {
                    volatileImageGraphics.dispose();
                }

                painted = !volatileImage.contentsLost();
            }
        }

        return painted;
    }

    /**
     * Paints the display including any effects.
     *
     * @param graphics
     */
    private void paintDisplay(Graphics2D graphics) {
        Graphics2D decoratedGraphics = graphics;

        Display display = this.apcx.getDisplay();

        Component.EffectSequence effects = display.getEffects();
        int n = effects.getLength();
        for (int i = n - 1; i >= 0; i--) {
            Effect effect = effects.get(i);
            decoratedGraphics = effect.prepare(display, decoratedGraphics);
        }

        display.paint(graphics);

        for (int i = 0; i < n; i++) {
            Effect effect = effects.get(i);
            effect.update();
        }

        // Paint the drag visual
        if (dragDescendant != null) {
            DragSource dragSource = dragDescendant.getDragSource();
            Visual dragRepresentation = dragSource.getRepresentation();

            if (dragRepresentation != null) {
                Point dragOffset = dragSource.getOffset();
                int tx = dragLocation.x - dragOffset.x;
                int ty = dragLocation.y - dragOffset.y;

                graphics.translate(tx, ty);
                dragRepresentation.paint(graphics);
            }
        }
    }

    private void repaintDragRepresentation() {
        DragSource dragSource = dragDescendant.getDragSource();
        Visual dragRepresentation = dragSource.getRepresentation();

        if (dragRepresentation != null) {
            Point dragOffset = dragSource.getOffset();

            repaint(dragLocation.x - dragOffset.x, dragLocation.y - dragOffset.y,
                    dragRepresentation.getWidth(), dragRepresentation.getHeight());
        }
    }

    private Component getDropDescendant(int x, int y) {
        Display display = this.apcx.getDisplay();

        Component dropDescendant = display.getDescendantAt(x, y);

        while (dropDescendant != null
               && dropDescendant.getDropTarget() == null) {
            dropDescendant = dropDescendant.getParent();
        }

        if (dropDescendant != null
            && dropDescendant.isBlocked()) {
            dropDescendant = null;
        }

        return dropDescendant;
    }

    private void startNativeDrag(final DragSource dragSource, final MouseEvent mouseEvent) {
        java.awt.dnd.DragSource awtDragSource = java.awt.dnd.DragSource.getDefaultDragSource();

        final int supportedDropActions = dragSource.getSupportedDropActions();

        DragGestureRecognizer dragGestureRecognizer =
            new DragGestureRecognizer(java.awt.dnd.DragSource.getDefaultDragSource(), this) {
                private static final long serialVersionUID = 0;
                {   appendEvent(mouseEvent);
                }

                public int getSourceActions() {
                    int awtSourceActions = 0;

                    if (DropAction.COPY.isSelected(supportedDropActions)) {
                        awtSourceActions |= DnDConstants.ACTION_COPY;
                    }

                    if (DropAction.MOVE.isSelected(supportedDropActions)) {
                        awtSourceActions |= DnDConstants.ACTION_MOVE;
                    }

                    if (DropAction.LINK.isSelected(supportedDropActions)) {
                        awtSourceActions |= DnDConstants.ACTION_LINK;
                    }

                    return awtSourceActions;
                }

                protected void registerListeners() {
                    // No-op
                }

                protected void unregisterListeners() {
                    // No-op
                }
            };

        java.util.List<InputEvent> inputEvents = new java.util.ArrayList<InputEvent>();
        inputEvents.add(mouseEvent);

        // TODO If current user drop action is supported by drag source, use it
        // as initial action - otherwise, select MOVE, COPY, LINK in that order
        java.awt.Point location = new java.awt.Point(mouseEvent.getX(), mouseEvent.getY());
        DragGestureEvent trigger = new DragGestureEvent(dragGestureRecognizer,
                                                        DnDConstants.ACTION_MOVE, location, inputEvents);

        LocalManifest dragContent = dragSource.getContent();
        LocalManifestAdapter localManifestAdapter = new LocalManifestAdapter(dragContent);

        awtDragSource.startDrag(trigger, java.awt.Cursor.getDefaultCursor(),
                                null, null, localManifestAdapter, new DragSourceListener() {
                                        public void dragEnter(DragSourceDragEvent event) {
                                            DragSourceContext context = event.getDragSourceContext();
                                            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
                                        }

                                        public void dragExit(DragSourceEvent event) {
                                            DragSourceContext context = event.getDragSourceContext();
                                            context.setCursor(java.awt.Cursor.getDefaultCursor());
                                        }

                                        public void dragOver(DragSourceDragEvent event) {
                                            DragSourceContext context = event.getDragSourceContext();
                                            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
                                        }

                                        public void dropActionChanged(DragSourceDragEvent event) {
                                            DragSourceContext context = event.getDragSourceContext();
                                            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
                                        }

                                        public void dragDropEnd(DragSourceDropEvent event) {
                                            DragSourceContext context = event.getDragSourceContext();
                                            context.setCursor(java.awt.Cursor.getDefaultCursor());
                                        }
                                    });
    }

    @Override
    protected void processComponentEvent(ComponentEvent event) {
        super.processComponentEvent(event);

        switch (event.getID()) {
        case ComponentEvent.COMPONENT_RESIZED: {
            this.apcx.getDisplay().setSize(getWidth(), getHeight());
            break;
        }

        case ComponentEvent.COMPONENT_MOVED: {
            // No-op
            break;
        }

        case ComponentEvent.COMPONENT_SHOWN: {
            // No-op
            break;
        }

        case ComponentEvent.COMPONENT_HIDDEN: {
            // No-op
            break;
        }
        }
    }

    @Override
    protected void processFocusEvent(FocusEvent event) {
        super.processFocusEvent(event);

        switch(event.getID()) {
        case FocusEvent.FOCUS_GAINED: {
            if (focusedComponent != null
                && focusedComponent.isShowing()
                && !focusedComponent.isBlocked()) {
                focusedComponent.requestFocus(true);
            }

            break;
        }

        case FocusEvent.FOCUS_LOST: {
            focusedComponent = Component.getFocusedComponent();
            Component.clearFocus(true);

            break;
        }
        }
    }

    @Override
    protected void processMouseEvent(MouseEvent event) {
        Display display = this.apcx.getDisplay();
        super.processMouseEvent(event);

        int x = event.getX();
        int y = event.getY();

        // Set the mouse button state
        int mouseButtons = 0x00;

        int modifiersEx = event.getModifiersEx();
        if ((modifiersEx & MouseEvent.BUTTON1_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.LEFT.getMask();
        }

        if ((modifiersEx & MouseEvent.BUTTON2_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.MIDDLE.getMask();
        }

        if ((modifiersEx & MouseEvent.BUTTON3_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.RIGHT.getMask();
        }

        Mouse.setButtons(mouseButtons);

        // Get the button associated with this event
        Mouse.Button button = null;
        switch (event.getButton()) {
        case MouseEvent.BUTTON1: {
            button = Mouse.Button.LEFT;
            break;
        }

        case MouseEvent.BUTTON2: {
            button = Mouse.Button.MIDDLE;
            break;
        }

        case MouseEvent.BUTTON3: {
            button = Mouse.Button.RIGHT;
            break;
        }
        }

        // Process the event
        int eventID = event.getID();
        if (eventID == MouseEvent.MOUSE_ENTERED
            || eventID == MouseEvent.MOUSE_EXITED) {
            switch(eventID) {
            case MouseEvent.MOUSE_ENTERED: {
                mouseLocation = new Point(x, y);
                display.mouseOver();
                break;
            }

            case MouseEvent.MOUSE_EXITED: {
                display.mouseOut();
                mouseLocation = null;
                break;
            }
            }
        } else {
            // Determine the mouse owner
            Component mouseOwner;
            Component mouseCapturer = Mouse.getCapturer();
            if (mouseCapturer == null) {
                mouseOwner = display;
            } else {
                mouseOwner = mouseCapturer;
                Point location = mouseOwner.mapPointFromAncestor(display, x, y);
                x = location.x;
                y = location.y;
            }

            // Delegate the event to the owner
            switch (eventID) {
            case MouseEvent.MOUSE_PRESSED: {
                requestFocus();
                dragLocation = new Point(x, y);
                mouseOwner.mouseDown(button, x, y);
                break;
            }

            case MouseEvent.MOUSE_RELEASED: {
                if (dragDescendant == null) {
                    mouseOwner.mouseUp(button, x, y);
                } else {
                    DragSource dragSource = dragDescendant.getDragSource();

                    repaintDragRepresentation();

                    if (dropDescendant == null) {
                        dragSource.endDrag(dragDescendant, null);
                    } else {
                        DropTarget dropTarget = dropDescendant.getDropTarget();
                        DropAction dropAction = dropTarget.drop(dropDescendant, dragManifest,
                                                                dragSource.getSupportedDropActions(), x, y, getUserDropAction(event));
                        dragSource.endDrag(dragDescendant, dropAction);
                    }

                    setCursor(java.awt.Cursor.getDefaultCursor());

                    // Clear the drag state
                    dragDescendant = null;
                    dragManifest = null;

                    // Clear the drop state
                    userDropAction = null;
                    dropDescendant = null;
                }

                // Clear the drag location
                dragLocation = null;

                break;
            }
            }
        }
    }

    @Override
    protected void processMouseMotionEvent(MouseEvent event) {
        Display display = this.apcx.getDisplay();
        super.processMouseMotionEvent(event);

        int x = event.getX();
        int y = event.getY();

        // Set the mouse location
        if (mouseLocation == null) {
            mouseLocation = new Point();
        }

        mouseLocation.x = x;
        mouseLocation.y = y;

        // Process the event
        switch (event.getID()) {
        case MouseEvent.MOUSE_MOVED:
        case MouseEvent.MOUSE_DRAGGED: {
            if (dragDescendant == null) {
                // A drag is not active
                Component mouseCapturer = Mouse.getCapturer();

                if (mouseCapturer == null) {
                    // The mouse is not captured, so propagate the event to the display
                    display.mouseMove(x, y);

                    int dragThreshold = Platform.getDragThreshold();

                    if (dragLocation != null
                        && (Math.abs(x - dragLocation.x) > dragThreshold
                            || Math.abs(y - dragLocation.y) > dragThreshold)) {
                        // The user has dragged the mouse past the drag threshold; try
                        // to find a drag source
                        dragDescendant = display.getDescendantAt(dragLocation.x,
                                                                 dragLocation.y);

                        while (dragDescendant != null
                               && dragDescendant.getDragSource() == null) {
                            dragDescendant = dragDescendant.getParent();
                        }

                        if (dragDescendant == null
                            || dragDescendant.isBlocked()) {
                            // There was nothing to drag, so clear the drag location
                            dragDescendant = null;
                            dragLocation = null;
                        } else {
                            DragSource dragSource = dragDescendant.getDragSource();
                            dragLocation = dragDescendant.mapPointFromAncestor(display, x, y);

                            if (dragSource.beginDrag(dragDescendant, dragLocation.x, dragLocation.y)) {
                                // A drag has started
                                if (dragSource.isNative()) {
                                    // Clear the drag state since it is not used for
                                    // native drags
                                    dragDescendant = null;
                                    dragLocation = null;

                                    startNativeDrag(dragSource, event);
                                } else {
                                    if (dragSource.getRepresentation() != null
                                        && dragSource.getOffset() == null) {
                                        throw new IllegalStateException("Drag offset is required when a "
                                                                        + " respresentation is specified.");
                                    }

                                    if (display.isMouseOver()) {
                                        display.mouseOut();
                                    }

                                    // Get the drag content
                                    dragManifest = dragSource.getContent();

                                    // Get the initial user drop action
                                    userDropAction = getUserDropAction(event);

                                    // Repaint the drag visual
                                    dragLocation.x = x;
                                    dragLocation.y = y;

                                    repaintDragRepresentation();
                                }
                            } else {
                                // Clear the drag state
                                dragDescendant = null;
                                dragLocation = null;
                            }
                        }
                    }
                } else {
                    // Delegate the event to the capturer
                    Point location = mouseCapturer.mapPointFromAncestor(display, x, y);
                    mouseCapturer.mouseMove(location.x, location.y);
                }
            } else {
                if (dragLocation != null) {
                    DragSource dragSource = dragDescendant.getDragSource();

                    // Get the previous and current drop descendant and call
                    // move or exit/enter as appropriate
                    Component previousDropDescendant = dropDescendant;
                    dropDescendant = getDropDescendant(x, y);

                    DropAction dropAction = null;

                    if (previousDropDescendant == dropDescendant) {
                        if (dropDescendant != null) {
                            DropTarget dropTarget = dropDescendant.getDropTarget();

                            Point dropLocation = dropDescendant.mapPointFromAncestor(display, x, y);
                            dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
                                                             dragSource.getSupportedDropActions(),
                                                             dropLocation.x, dropLocation.y, userDropAction);
                        }
                    } else {
                        if (previousDropDescendant != null) {
                            DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
                            previousDropTarget.dragExit(previousDropDescendant);
                        }

                        if (dropDescendant != null) {
                            DropTarget dropTarget = dropDescendant.getDropTarget();
                            dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                                              dragSource.getSupportedDropActions(), userDropAction);
                        }
                    }

                    // Update cursor
                    setCursor(getDropCursor(dropAction));

                    // Repaint the drag visual
                    repaintDragRepresentation();

                    dragLocation.x = x;
                    dragLocation.y = y;

                    repaintDragRepresentation();
                }
            }

            break;
        }
        }
    }

    @Override
    protected void processMouseWheelEvent(MouseWheelEvent event) {
        Display display = this.apcx.getDisplay();
        super.processMouseWheelEvent(event);

        // Get the event coordinates
        int x = event.getX();
        int y = event.getY();

        // Get the scroll type
        Mouse.ScrollType scrollType = null;
        switch (event.getScrollType()) {
        case MouseWheelEvent.WHEEL_BLOCK_SCROLL: {
            scrollType = Mouse.ScrollType.BLOCK;
            break;
        }

        case MouseWheelEvent.WHEEL_UNIT_SCROLL: {
            scrollType = Mouse.ScrollType.UNIT;
            break;
        }
        }

        // Process the event
        switch (event.getID()) {
        case MouseEvent.MOUSE_WHEEL: {
            if (dragDescendant == null) {
                // Determine the mouse owner
                Component mouseOwner;
                Component mouseCapturer = Mouse.getCapturer();
                if (mouseCapturer == null) {
                    mouseOwner = display;
                } else {
                    mouseOwner = mouseCapturer;
                    Point location = mouseOwner.mapPointFromAncestor(display, x, y);
                    x = location.x;
                    y = location.y;
                }

                // Delegate the event to the owner
                mouseOwner.mouseWheel(scrollType, event.getScrollAmount(),
                                      event.getWheelRotation(), x, y);
            }
            break;
        }
        }
    }

    @Override
    protected void processKeyEvent(KeyEvent event) {
        Display display = this.apcx.getDisplay();
        super.processKeyEvent(event);

        // Set the keyboard modifier state
        int keyboardModifiers = 0;

        int modifiersEx = event.getModifiersEx();
        if ((modifiersEx & KeyEvent.SHIFT_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.SHIFT.getMask();
        }

        if ((modifiersEx & KeyEvent.CTRL_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.CTRL.getMask();
        }

        if ((modifiersEx & KeyEvent.ALT_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.ALT.getMask();
        }

        if ((modifiersEx & KeyEvent.META_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.META.getMask();
        }

        Keyboard.setModifiers(keyboardModifiers);

        // Get the key location
        Keyboard.KeyLocation keyLocation = null;
        switch (event.getKeyLocation()) {
        case KeyEvent.KEY_LOCATION_STANDARD: {
            keyLocation = Keyboard.KeyLocation.STANDARD;
            break;
        }

        case KeyEvent.KEY_LOCATION_LEFT: {
            keyLocation = Keyboard.KeyLocation.LEFT;
            break;
        }

        case KeyEvent.KEY_LOCATION_RIGHT: {
            keyLocation = Keyboard.KeyLocation.RIGHT;
            break;
        }

        case KeyEvent.KEY_LOCATION_NUMPAD: {
            keyLocation = Keyboard.KeyLocation.KEYPAD;
            break;
        }
        }

        if (dragDescendant == null) {
            // Process the event
            Component focusedComponent = Component.getFocusedComponent();

            switch (event.getID()) {
            case KeyEvent.KEY_PRESSED: {
                int keyCode = event.getKeyCode();
                boolean consumed = false;

                if (focusedComponent != null) {
                    consumed = focusedComponent.keyPressed(keyCode, keyLocation);
                }

                if (consumed) {
                    event.consume();
                }

                break;
            }

            case KeyEvent.KEY_RELEASED: {
                int keyCode = event.getKeyCode();
                boolean consumed = false;

                if (focusedComponent != null) {
                    consumed = focusedComponent.keyReleased(keyCode, keyLocation);
                }

                if (consumed) {
                    event.consume();
                }

                break;
            }

            case KeyEvent.KEY_TYPED: {
                boolean consumed = false;

                if (focusedComponent != null) {
                    char keyChar = event.getKeyChar();
                    consumed = focusedComponent.keyTyped(keyChar);
                }

                if (consumed) {
                    event.consume();
                }

                break;
            }
            }
        } else {
            DragSource dragSource = dragDescendant.getDragSource();

            // If the user drop action changed, notify the drop descendant
            if (dropDescendant != null) {
                DropAction previousUserDropAction = userDropAction;
                userDropAction = getUserDropAction(event);

                if (previousUserDropAction != userDropAction) {
                    DropTarget dropTarget = dropDescendant.getDropTarget();

                    Point dropLocation = dropDescendant.mapPointFromAncestor(display,
                                                                             mouseLocation.x, mouseLocation.y);
                    dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                    dragSource.getSupportedDropActions(),
                                                    dropLocation.x, dropLocation.y, userDropAction);
                }
            }
        }
    }
}
